/* SSTV PD90 mode bitmap encoder using a VS1003-based audio module
 * (Jaycar: XC4516)
 * --
 * Chris Baird, June-July 2022 <vk2cjb@gmail.com>
 * Usage as per the GPLv3.
 */

// based on vs1003_hello created 2012-04-05 by Andrey Karpov
// VS1003 development board connected thusly:
//
//    GND  - GND   5V   - 5V
//    XDCS - D6    DREQ - D7
//    XRES - D8    XCS  - D9
//    SI   - D11   SO   - D12
//    SCLK - D13

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <Arduino.h>
#include <SPI.h>
#include "VS1003.h"
#include "bitmap.h"

VS1003 player(9, 6, 7, 8);     // cs_pin, dcs_pin, dreq_pin, reset_pin

#define TONESIZE (16) // seems that using anything else causes distortion
uint16_t tonebuf[TONESIZE];

#define samplebytes (2)
#define channels (1)
#define samplerate (8000)
#define datarate (samplerate*channels*samplebytes)

const uint8_t wavheader[] =
  {
   'R','I','F','F',
   0x00,0x00,0x00,0x00,		// wav size (0=play indefinitely)
   'W','A','V','E', 'f','m','t',' ',
   16, 0, 0, 0,			// fmt_chunk_size
   1, 0,			// audio format = PCM
   1, 0,			// num_channels
   samplerate&255, (samplerate>>8)&255, samplerate>>16,0,
   datarate&255, (datarate>>8)&255, datarate>>16,0,
   samplebytes, 0,		// sample_alignment
   samplebytes<<3, 0,
   'd','a','t','a',
   0x00,0x00,0x00,0x00		// data size (0=infinite)
  };

// PD90
#define VISCODE (99)
#define scanlen (1362)

// #!/usr/bin/env python3.9
// import math
// N,r,s = 256,3276.8*2,""
// for i in range(N):
//     v = int(math.sin(i*math.tau/N)*r)
//     if v < 0: v = 65536+v
//     s += "0x{:04X}, ".format(v)
// print (s)

#define SINESIZE (256)
const PROGMEM uint16_t sine[SINESIZE] =
  {
    0x0000, 0x00A0, 0x0141, 0x01E2, 0x0282, 0x0322, 0x03C1, 0x0460, 0x04FE,
    0x059B, 0x0638, 0x06D3, 0x076E, 0x0807, 0x089F, 0x0936, 0x09CB, 0x0A5F,
    0x0AF2, 0x0B82, 0x0C11, 0x0C9E, 0x0D29, 0x0DB2, 0x0E38, 0x0EBD, 0x0F3F,
    0x0FBF, 0x103D, 0x10B8, 0x1131, 0x11A6, 0x121A, 0x128A, 0x12F7, 0x1362,
    0x13CA, 0x142E, 0x148F, 0x14EE, 0x1549, 0x15A0, 0x15F5, 0x1646, 0x1693,
    0x16DD, 0x1724, 0x1767, 0x17A6, 0x17E2, 0x181A, 0x184E, 0x187F, 0x18AC,
    0x18D5, 0x18FA, 0x191B, 0x1939, 0x1952, 0x1968, 0x197A, 0x1987, 0x1991,
    0x1997, 0x1999, 0x1997, 0x1991, 0x1987, 0x197A, 0x1968, 0x1952, 0x1939,
    0x191B, 0x18FA, 0x18D5, 0x18AC, 0x187F, 0x184E, 0x181A, 0x17E2, 0x17A6,
    0x1767, 0x1724, 0x16DD, 0x1693, 0x1646, 0x15F5, 0x15A0, 0x1549, 0x14EE,
    0x148F, 0x142E, 0x13CA, 0x1362, 0x12F7, 0x128A, 0x121A, 0x11A6, 0x1131,
    0x10B8, 0x103D, 0x0FBF, 0x0F3F, 0x0EBD, 0x0E38, 0x0DB2, 0x0D29, 0x0C9E,
    0x0C11, 0x0B82, 0x0AF2, 0x0A5F, 0x09CB, 0x0936, 0x089F, 0x0807, 0x076E,
    0x06D3, 0x0638, 0x059B, 0x04FE, 0x0460, 0x03C1, 0x0322, 0x0282, 0x01E2,
    0x0141, 0x00A0, 0x0000, 0xFF60, 0xFEBF, 0xFE1E, 0xFD7E, 0xFCDE, 0xFC3F,
    0xFBA0, 0xFB02, 0xFA65, 0xF9C8, 0xF92D, 0xF892, 0xF7F9, 0xF761, 0xF6CA,
    0xF635, 0xF5A1, 0xF50E, 0xF47E, 0xF3EF, 0xF362, 0xF2D7, 0xF24E, 0xF1C8,
    0xF143, 0xF0C1, 0xF041, 0xEFC3, 0xEF48, 0xEECF, 0xEE5A, 0xEDE6, 0xED76,
    0xED09, 0xEC9E, 0xEC36, 0xEBD2, 0xEB71, 0xEB12, 0xEAB7, 0xEA60, 0xEA0B,
    0xE9BA, 0xE96D, 0xE923, 0xE8DC, 0xE899, 0xE85A, 0xE81E, 0xE7E6, 0xE7B2,
    0xE781, 0xE754, 0xE72B, 0xE706, 0xE6E5, 0xE6C7, 0xE6AE, 0xE698, 0xE686,
    0xE679, 0xE66F, 0xE669, 0xE667, 0xE669, 0xE66F, 0xE679, 0xE686, 0xE698,
    0xE6AE, 0xE6C7, 0xE6E5, 0xE706, 0xE72B, 0xE754, 0xE781, 0xE7B2, 0xE7E6,
    0xE81E, 0xE85A, 0xE899, 0xE8DC, 0xE923, 0xE96D, 0xE9BA, 0xEA0B, 0xEA60,
    0xEAB7, 0xEB12, 0xEB71, 0xEBD2, 0xEC36, 0xEC9E, 0xED09, 0xED76, 0xEDE6,
    0xEE5A, 0xEECF, 0xEF48, 0xEFC3, 0xF041, 0xF0C1, 0xF143, 0xF1C8, 0xF24E,
    0xF2D7, 0xF362, 0xF3EF, 0xF47E, 0xF50E, 0xF5A1, 0xF635, 0xF6CA, 0xF761,
    0xF7F9, 0xF892, 0xF92D, 0xF9C8, 0xFA65, 0xFB02, 0xFBA0, 0xFC3F, 0xFCDE,
    0xFD7E, 0xFE1E, 0xFEBF, 0xFF60
  };

// ----------------------------------------------------------------------

void setup ()
{
  SPI.begin();
  player.begin();
  player.setVolume(0);
  player.playChunk(wavheader,sizeof(wavheader));
}

// ----------------------------------------------------------------------

void sampleout (uint16_t freq)
{
  static float iphase = 0;
  static int bp = 0;

  iphase += ((float)SINESIZE*(float)freq) / (float)samplerate;
  int i = iphase;
  int j = iphase / SINESIZE;
  int k = i - j*SINESIZE;
  tonebuf[bp] = pgm_read_word_near(sine + k);
  while (iphase >= SINESIZE) iphase -= SINESIZE;

  if (++bp == TONESIZE)
    {
      player.playChunk((const uint8_t*)tonebuf, sizeof(tonebuf));
      bp = 0;
    }
}


void tonegen (float freq, uint16_t samples)
{
  while (samples--) sampleout(freq);
}

// ----------------------------------------------------------------------

void vis(uint8_t c)
{
  uint16_t i, p = 0;

  tonegen(1900,2400);		// 300ms @ 8kHz = 2400 samples
  tonegen(1200,80);		// 10ms
  tonegen(1900,2400);		// 300ms
  tonegen(1200,240);		// 30ms

  for (i = 1; i != 256; i <<= 1)
    {
      if ((i == 128) && (p & 1)) c |= 128;
      if (c & i)
	{
	  p++;
	  tonegen(1100,240);
	}
      else tonegen(1300,240);
    }
  tonegen(1200,240);
}

// ----------------------------------------------------------------------

void doline(int y)
{
  int p = y * 40;
  uint8_t c = 0, bm = 0;
  uint16_t tf = 0;

  for (uint16_t x = 0; x < scanlen; x++)
    {
      if (tf >= 256)
	{
	  bm <<= 1;
	  tf -= 256;
	}
      tf += ((uint32_t)320 << 8) / scanlen;

      if (bm == 0)
	{
	  c = (uint8_t)pgm_read_byte(&screenbits[p++]);
	  bm = 1;
	}

      if (c & bm) sampleout(2300); else sampleout(1700);
    }
}

// ----------------------------------------------------------------------

void loop()
{
  tonegen(1000,samplerate>>1);	// trigger VOX
  tonegen(0,samplerate>>1);
  vis(VISCODE);

  for (int y = 0; y < 256; y += 2)
    {
      tonegen(1200,160);
      tonegen(1500,17);

      doline(y);
      tonegen(1900,scanlen);	// Cr
      tonegen(2100,scanlen);	// Cb
      doline(y+1);
    }

  tonegen(0,samplerate);
  delay(30000);
}

// ----------------------------------------------------------------------
